﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;

namespace Xceed.Silverlight.Data
{
  internal sealed class DataSourceProviderDataHelper : IContextualDataHelper<object, RawDataItem>
  {
    #region Constructor

    internal DataSourceProviderDataHelper( DataSourceProvider dataSourceProvider )
    {
      if( dataSourceProvider == null )
        throw new ArgumentNullException( "dataSourceProvider" );

      m_dataSourceProvider = dataSourceProvider;
    }

    #endregion

    private static int Compare( object firstValue, object secondValue, bool reverse )
    {
      if( object.ReferenceEquals( firstValue, secondValue ) )
        return 0;

      int compare;

      if( firstValue == null )
      {
        compare = -1;
      }
      else if( secondValue == null )
      {
        compare = 1;
      }
      else
      {
        compare = ( ( IComparable )firstValue ).CompareTo( secondValue );
      }

      if( reverse )
        return -compare;

      return compare;
    }

    private int Compare( DataSourceContext context, RawDataItem item, SortPosition position )
    {
      SortDescription[] currentSortOrder = context.GetCurrentCompleteSortOrder().ToArray();
      IList<SortDescription> positionSortOrder = position.SortingReference;

      // The SortPosition cannot have more sorting fields than the current context.
      if( positionSortOrder.Count > currentSortOrder.Length )
        throw new DataInternalException();

      IList<object> positionSortValues = position.SortValues;
      int compare = 0;

      for( int i = 0; i < currentSortOrder.Length; i++ )
      {
        SortDescription sortOrder = currentSortOrder[ i ];

        // Make sure the target fields are appropriate for the context.
        if( ( i < positionSortOrder.Count ) && ( sortOrder != positionSortOrder[ i ] ) )
          throw new DataInternalException();

        // Find out which position comes first if we don't know already.
        if( ( compare == 0 ) && ( i < positionSortValues.Count ) )
        {
          bool descending = ( sortOrder.Direction == ListSortDirection.Descending );
          object itemValue = this.GetMemberValue( context, sortOrder.PropertyName, item );

          compare = DataSourceProviderDataHelper.Compare( itemValue, positionSortValues[ i ], descending );
        }
      }

      // Find out if we are able to determine which position comes first without
      // comparing the values.
      if( compare == 0 )
      {
        compare = ( position.StartPosition == StartPosition.Before ) ? 1 : -1;
      }

      return compare;
    }

    private int Compare( DataSourceContext context, SortPosition firstPosition, SortPosition secondPosition )
    {
      if( object.ReferenceEquals( firstPosition, secondPosition ) )
        return 0;

      SortDescription[] currentSortOrder = context.GetCurrentCompleteSortOrder().ToArray();
      IList<SortDescription> firstPositionSortOrder = firstPosition.SortingReference;
      IList<SortDescription> secondPositionSortOrder = secondPosition.SortingReference;

      // The SortPosition cannot have more sorting fields than the current context.
      if( ( firstPositionSortOrder.Count > currentSortOrder.Length )
        || ( secondPositionSortOrder.Count > currentSortOrder.Length ) )
        throw new DataInternalException();

      IList<object> firstPositionSortValues = firstPosition.SortValues;
      IList<object> secondPositionSortValues = secondPosition.SortValues;
      int valuesCount = Math.Min( firstPositionSortValues.Count, secondPositionSortValues.Count );
      int compare = 0;

      for( int i = 0; i < currentSortOrder.Length; i++ )
      {
        SortDescription sortOrder = currentSortOrder[ i ];

        // Make sure the target fields are appropriate for the context.
        if( ( ( i < firstPositionSortOrder.Count ) && ( sortOrder != firstPositionSortOrder[ i ] ) )
          || ( ( i < secondPositionSortOrder.Count ) && ( sortOrder != secondPositionSortOrder[ i ] ) ) )
          throw new DataInternalException();

        // Find out which position comes first if we don't know already.
        if( ( compare == 0 ) && ( i < valuesCount ) )
        {
          bool descending = ( sortOrder.Direction == ListSortDirection.Descending );

          compare = DataSourceProviderDataHelper.Compare( firstPositionSortValues[ i ], secondPositionSortValues[ i ], descending );
        }
      }

      // Find out if we are able to determine which position comes first without
      // comparing the values.
      if( compare == 0 )
      {
        // Check out if a SortPosition isn't "inside" the other SortPosition
        if( firstPositionSortValues.Count < secondPositionSortValues.Count )
        {
          compare = ( firstPosition.StartPosition == StartPosition.Before ) ? -1 : 1;
        }
        // Check out if a SortPosition isn't "inside" the other SortPosition
        else if( secondPositionSortValues.Count < firstPositionSortValues.Count )
        {
          compare = ( secondPosition.StartPosition == StartPosition.Before ) ? 1 : -1;
        }
        else if( firstPosition.StartPosition != secondPosition.StartPosition )
        {
          compare = ( firstPosition.StartPosition == StartPosition.Before ) ? -1 : 1;
        }
      }

      return compare;
    }

    #region IContextualItemHelper<RawDataItem> Members

    public object GetMemberValue(
      DataSourceContext context,
      string propertyPath,
      RawDataItem item )
    {
      if( item == null )
        throw new ArgumentNullException( "item" );

      return item.GetDataMemberValue( propertyPath );
    }

    public bool TryGetMemberValue(
      DataSourceContext context,
      string propertyPath,
      RawDataItem item,
      out object value )
    {
      if( item == null )
        throw new ArgumentNullException( "item" );

      return item.TryGetDataMemberValue( propertyPath, out value );
    }

    #endregion

    #region IContextualConvertHelper<object, RawDataItem> Members

    public RawDataItem Convert( DataSourceContext context, object value )
    {
      return m_dataSourceProvider.WrapDataObject( value );
    }

    public object ConvertBack( DataSourceContext context, RawDataItem value )
    {
      return m_dataSourceProvider.UnwrapDataObject( value );
    }

    public RawDataItem DeepConvert( DataSourceContext context, object value )
    {
      return this.Convert( context, value );
    }

    public object DeepConvertBack( DataSourceContext context, RawDataItem value )
    {
      return this.ConvertBack( context, value );
    }

    #endregion

    #region IContextualCompareHelper<RawDataItem> Members

    public int Compare( DataSourceContext context, RawDataItem firstItem, RawDataItem secondItem )
    {
      if( !object.ReferenceEquals( firstItem, secondItem ) )
      {
        foreach( SortDescription sortOrder in context.GetCurrentCompleteSortOrder() )
        {
          string propertyPath = sortOrder.PropertyName;
          object firstValue = this.GetMemberValue( context, propertyPath, firstItem );
          object secondValue = this.GetMemberValue( context, propertyPath, secondItem );

          bool descending = ( sortOrder.Direction == ListSortDirection.Descending );
          int compare = DataSourceProviderDataHelper.Compare( firstValue, secondValue, descending );

          if( compare != 0 )
            return compare;
        }
      }

      return 0;
    }

    public int Compare( DataSourceContext context, RawDataItem item, SourcePosition position )
    {
      ItemPosition itemPosition = position as ItemPosition;
      if( itemPosition != null )
        return this.Compare( context, item, ( RawDataItem )itemPosition.Item );

      SortPosition sortPosition = position as SortPosition;
      if( sortPosition != null )
        return this.Compare( context, item, sortPosition );

      if( position.IsAfterLast )
        return -1;

      if( position.IsBeforeFirst )
        return 1;

      throw new DataInternalException();
    }

    public int Compare( DataSourceContext context, SourcePosition position, RawDataItem item )
    {
      return -this.Compare( context, item, position );
    }

    public int Compare( DataSourceContext context, SourcePosition firstPosition, SourcePosition secondPosition )
    {
      if( object.ReferenceEquals( firstPosition, secondPosition ) )
        return 0;

      if( firstPosition.IsBeforeFirst )
        return -1;

      if( firstPosition.IsAfterLast )
        return 1;

      ItemPosition firstItemPosition = firstPosition as ItemPosition;
      if( firstItemPosition != null )
      {
        RawDataItem item = ( RawDataItem )firstItemPosition.Item;

        ItemPosition secondItemPosition = secondPosition as ItemPosition;
        if( secondItemPosition != null )
          return this.Compare( context, item, ( RawDataItem )secondItemPosition.Item );

        SortPosition secondSortPosition = secondPosition as SortPosition;
        if( secondSortPosition != null )
          return this.Compare( context, item, secondSortPosition );
      }

      SortPosition firstSortPosition = firstPosition as SortPosition;
      if( firstSortPosition != null )
      {
        ItemPosition secondItemPosition = secondPosition as ItemPosition;
        if( secondItemPosition != null )
          return -this.Compare( context, ( RawDataItem )secondItemPosition.Item, firstSortPosition );

        SortPosition secondSortPosition = secondPosition as SortPosition;
        if( secondSortPosition != null )
          return this.Compare( context, firstSortPosition, secondSortPosition );
      }

      throw new DataInternalException();
    }

    public int Compare( DataSourceContext context, string propertyPath, object firstValue, object secondValue )
    {
      if( object.ReferenceEquals( firstValue, secondValue ) )
        return 0;

      bool descending = ( from sd in context.GetCurrentCompleteSortOrder()
                          where ( sd.PropertyName == propertyPath )
                          select ( sd.Direction == ListSortDirection.Descending ) ).SingleOrDefault();

      return DataSourceProviderDataHelper.Compare( firstValue, secondValue, descending );
    }

    #endregion

    #region IContextualEditHelper<RawDataItem> Members

    public void Invalidate( DataSourceContext context, RawDataItem item )
    {
      m_dataSourceProvider.InvalidateDataItem( item );
    }

    #endregion

    #region Private Fields

    private readonly DataSourceProvider m_dataSourceProvider;

    #endregion
  }
}
